﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using Tesla.Content;
using Tesla.Math;

namespace Tesla.Bounding {
    /// <summary>
    /// An Axis-Aligned Bounding Box. These are not as simple as a bounding sphere but may encapsulate
    /// a mesh better. The box expands/shrinks along the standard X,Y,Z unit axes if the mesh it
    /// represents scales/rotates.
    /// </summary>
    public sealed class BoundingBox : BoundingVolume {
        private Vector3 m_extents;

        /// <summary>
        /// Gets or sets the extents of the bounding box (half-lengths along each axis from the center).
        /// </summary>
        public Vector3 Extents {
            get {
                return m_extents;
            }
            set {
                m_extents = value;
            }
        }

        /// <summary>
        /// Gets the maximum x, y, z points.
        /// </summary>
        public Vector3 Max {
            get {
                return new Vector3(m_center.X + m_extents.X, m_center.Y + m_extents.Y, m_center.Z + m_extents.Z);
            }
        }

        /// <summary>
        /// Gets the minimum x, y, z points.
        /// </summary>
        public Vector3 Min {
            get {
                return new Vector3(m_center.X - m_extents.X, m_center.Y - m_extents.Y, m_center.Z - m_extents.Z);
            }
        }

        /// <summary>
        /// Gets the volume of the bounding volume.
        /// </summary>
        public override float Volume {
            get {
                return 8 * ((m_extents.X * m_extents.Y * m_extents.Z) * 2);
            }
        }

        /// <summary>
        /// Gets the bounding type.
        /// </summary>
        public override BoundingType BoundingType {
            get {
                return BoundingType.AABB;
            }
        }

        /// <summary>
        /// Creates a new instance of bounding box, centered at the origin with zero extents.
        /// </summary>
        public BoundingBox() 
            : base() {
                m_extents = Vector3.Zero;
        }

        /// <summary>
        /// Creates a new instance of bounding box.
        /// </summary>
        /// <param name="center">Center of the box</param>
        /// <param name="extents">Half-lengths along each axis</param>
        public BoundingBox(Vector3 center, Vector3 extents)
            : base(center) {
                m_extents = extents;
        }

        /// <summary>
        /// Creates a new instance of bounding box from the source box.
        /// </summary>
        /// <param name="source">Source to copy from</param>
        public BoundingBox(BoundingBox source) {
            m_center = source.Center;
            m_extents = source.Extents;
        }

        /// <summary>
        /// Sets this bounding volume to contain the specified volume or to copy from.
        /// </summary>
        /// <param name="volume">Volume to copy from</param>
        public override void Set(BoundingVolume volume) {
            switch(volume.BoundingType) {
                case BoundingType.AABB:
                    BoundingBox bb = volume as BoundingBox;
                    m_center = bb.Center;
                    m_extents = bb.Extents;
                    return;
                case BoundingType.Sphere:
                    BoundingSphere sphere = volume as BoundingSphere;
                    m_center = sphere.Center;
                    float r = sphere.Radius;
                    m_extents = new Vector3(r, r, r);
                    return;
                case BoundingType.OBB:
                    OrientedBoundingBox obb = volume as OrientedBoundingBox;
                    if(!obb.CorrectCorners) {
                        obb.ComputeCorners();
                    }
                    ComputeFromPoints(obb.Corners);
                    return;
            }
        }

        /// <summary>
        /// Sets this bounding box to the specified center and x,y,z extents.
        /// </summary>
        /// <param name="center">New center of the box</param>
        /// <param name="extents">New x, y, z extents</param>
        public void Set(Vector3 center, Vector3 extents) {
            m_center = center;
            m_extents = extents;
        }

        /// <summary>
        /// Returns a new instance of this bounding volume.
        /// </summary>
        /// <returns>Copied bounding volume</returns>
        public override BoundingVolume Clone() {
            return new BoundingBox(this);
        }

        /// <summary>
        /// Compute the distance to the nearest edge of the volume
        /// from the point.
        /// </summary>
        /// <param name="point">Vector3</param>
        /// <returns>Distance from neatest edge</returns>
        public override float DistanceToEdge(Vector3 point) {
            Vector3 edge = point - m_center;
            float sqrDistance = 0.0f;
            float delta;

            float xExtent = m_extents.X;
            float yExtent = m_extents.Y;
            float zExtent = m_extents.Z;

            float x = edge.X;
            float y = edge.Y;
            float z = edge.Z;

            if(x < -xExtent) {
                delta = x + xExtent;
                sqrDistance += delta * delta;
            } else if(x > xExtent) {
                delta = x - xExtent;
                sqrDistance += delta * delta;
            }

            if(y < -yExtent) {
                delta = y + yExtent;
                sqrDistance += delta * delta;
            } else if(y > yExtent) {
                delta = y - yExtent;
                sqrDistance += delta * delta;
            }

            if(z < -zExtent) {
                delta = z + zExtent;
                sqrDistance += delta * delta;
            } else if(z > zExtent) {
                delta = z - zExtent;
                sqrDistance += delta * delta;
            }

            return MathHelper.Sqrt(sqrDistance);
        }

        /// <summary>
        /// Computes this bounding volume from a set of 3D points
        /// </summary>
        /// <param name="points">Array of Vectors</param>
        /// <exception cref="System.InvalidOperationException">Thrown if points are null, or if there are less than two points in the array.</exception>
        public override void ComputeFromPoints(Vector3[] points) {
            if((points == null) || points.Length < 2) {
                throw new InvalidOperationException("Cannot compute bounding box, points are null or not of sufficient count (at least 2 points are needed)");
            }

            Vector3 min = new Vector3(float.MaxValue);
            Vector3 max = new Vector3(float.MinValue);

            for(int i = 0; i < points.Length; i++) {
                Vector3 v = points[i];
                Vector3.Min(ref min, ref v, out min);
                Vector3.Max(ref max, ref v, out max);
            }

            //Compute center from min-max
            Vector3.Add(ref max, ref min, out m_center);
            Vector3.Multiply(ref m_center, .5f, out m_center);

            //Compute extents from min-max
            Vector3.Subtract(ref max, ref m_center, out m_extents);
        }

        /// <summary>
        /// Compute this bounding volume from a set of primitives.
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="indices">Index buffer</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the vertices or indices are null.</exception>
        public override void ComputeFromPrimitives(Vector3[] vertices, int[] indices) {

            if((vertices == null) || (indices == null)) {
                throw new InvalidOperationException("Cannot compute bounding box, both vertices and indices must exist.");
            }


            Vector3 min = new Vector3(float.MaxValue);
            Vector3 max = new Vector3(float.MinValue);

            for(int i = 0; i < indices.Length; i++) {
                Vector3 v = vertices[indices[i]];
                Vector3.Min(ref min, ref v, out min);
                Vector3.Max(ref max, ref v, out max);
            }

            //Compute center from min-max
            Vector3.Add(ref max, ref min, out m_center);
            Vector3.Multiply(ref m_center, .5f, out m_center);

            //Compute extents from min-max
            Vector3.Subtract(ref max, ref m_center, out m_extents);
        }

        /// <summary>
        /// Compute this bounding volume from a set of primitives.
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="indices">Index buffer</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the verticesor indices are null.</exception>
        public override void ComputeFromPrimitives(Vector3[] vertices, short[] indices) {

            if((vertices == null) || (indices == null)) {
                throw new InvalidOperationException("Cannot compute bounding box, both vertices and indices must exist.");
            }

            Vector3 min = new Vector3(float.MaxValue);
            Vector3 max = new Vector3(float.MinValue);

            for(int i = 0; i < indices.Length; i++) {
                Vector3 v = vertices[indices[i]];
                Vector3.Min(ref min, ref v, out min);
                Vector3.Max(ref max, ref v, out max);
            }

            //Compute center from min-max
            Vector3.Add(ref max, ref min, out m_center);
            Vector3.Multiply(ref m_center, .5f, out m_center);

            //Compute extents from min-max
            Vector3.Subtract(ref max, ref m_center, out m_extents);
        }

        /// <summary>
        /// Tests if the bounding box intersects with this bounding box.
        /// </summary>
        /// <param name="box">AABB to test</param>
        /// <returns>True if they intersect</returns>
        public override bool Intersects(BoundingBox box) {
            if(box == null) {
                return false;
            }

            Vector3 bbCenter = box.Center;
            Vector3 bbExtents = box.Extents;

            if(m_center.X + m_extents.X < bbCenter.X - bbExtents.X
                || m_center.X - m_extents.X > bbCenter.X + bbExtents.X) {
                    return false;
            } else if(m_center.Y + m_extents.Y < bbCenter.Y - bbExtents.Y
                || m_center.Y - m_extents.Y > bbCenter.Y + bbExtents.Y) {
                    return false;
            } else if(m_center.Z + m_extents.Z < bbCenter.Z - bbExtents.Z
                || m_center.Z - m_extents.Z > bbCenter.Z + bbExtents.Z) {
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Tests if the bounding sphere intersects with this bounding box.
        /// </summary>
        /// <param name="sphere">Sphere to test</param>
        /// <returns>True if they intersect</returns>
        public override bool Intersects(BoundingSphere sphere) {
            if(sphere == null) {
                return false;
            }

            Vector3 sCenter = sphere.Center;
            float sRad = sphere.Radius;

            if(System.Math.Abs(m_center.X - sCenter.X) < sRad + m_extents.X
                && System.Math.Abs(m_center.Y - sCenter.Y) < sRad + m_extents.Y
                && System.Math.Abs(m_center.Z - sCenter.Z) < sRad + m_extents.Z) {
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Tests if the oriented bounding box intersects with this bounding box.
        /// </summary>
        /// <param name="obb">Oriented bounding box to test</param>
        /// <returns>True if they intersect</returns>
        public override bool Intersects(OrientedBoundingBox obb) {
            if(obb == null) {
                return false;
            }

            return obb.Intersects(this);
        }

        /// <summary>
        /// Tests if the ray intersects with this bounding volume.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <returns>True if they intersect</returns>
        public override bool Intersects(Ray ray) {
            float xExtent = m_extents.X;
            float yExtent = m_extents.Y;
            float zExtent = m_extents.Z;

            Vector3 diff = ray.Origin - m_center;

            float fWdU0 = Vector3.Dot(ray.Direction, Vector3.UnitX);
            float fAWdU0 = System.Math.Abs(fWdU0);
            float fDdU0 = Vector3.Dot(diff, Vector3.UnitX);
            float fADdU0 = System.Math.Abs(fDdU0);

            if(fADdU0 > xExtent && (fDdU0 * fWdU0 >= 0.0f)) {
                return false;
            }

            float fWdU1 = Vector3.Dot(ray.Direction, Vector3.UnitY);
            float fAWdU1 = System.Math.Abs(fWdU1);
            float fDdU1 = Vector3.Dot(diff, Vector3.UnitY);
            float fADdU1 = System.Math.Abs(fDdU1);

            if(fADdU1 > yExtent && (fDdU1 * fWdU1 >= 0.0f)) {
                return false;
            }

            float fWdU2 = Vector3.Dot(ray.Direction, Vector3.UnitZ);
            float fAWdU2 = System.Math.Abs(fWdU2);
            float fDdU2 = Vector3.Dot(diff, Vector3.UnitZ);
            float fADdU2 = System.Math.Abs(fDdU2);

            if(fADdU2 > zExtent && (fDdU2 * fWdU2 >= 0.0f)) {
                return false;
            }

            Vector3 wCrossD = Vector3.Cross(ray.Direction, diff);

            float fAWxDdU0 = System.Math.Abs(Vector3.Dot(wCrossD, Vector3.UnitX));
            float rhs = (yExtent * fAWdU2) + (zExtent * fAWdU1);
            if(fAWxDdU0 > rhs) {
                return false;
            }

            float fAWxDdU1 = System.Math.Abs(Vector3.Dot(wCrossD, Vector3.UnitY));
            rhs = (xExtent * fAWdU2) + (zExtent * fAWdU0);
            if(fAWxDdU1 > rhs) {
                return false;
            }

            float fAWxDdU2 = System.Math.Abs(Vector3.Dot(wCrossD, Vector3.UnitZ));
            rhs = (xExtent * fAWdU1) + (yExtent * fAWdU0);
            if(fAWxDdU2 > rhs) {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Tests if the ray intersects with this bounding volume.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <param name="result">Result, true if they intersect</param>
        public override void Intersects(ref Ray ray, out bool result) {
            float xExtent = m_extents.X;
            float yExtent = m_extents.Y;
            float zExtent = m_extents.Z;

            Vector3 diff = ray.Origin - m_center;

            float fWdU0 = Vector3.Dot(ray.Direction, Vector3.UnitX);
            float fAWdU0 = System.Math.Abs(fWdU0);
            float fDdU0 = Vector3.Dot(diff, Vector3.UnitX);
            float fADdU0 = System.Math.Abs(fDdU0);

            if(fADdU0 > xExtent && (fDdU0 * fWdU0 >= 0.0f)) {
                result = false;
            }

            float fWdU1 = Vector3.Dot(ray.Direction, Vector3.UnitY);
            float fAWdU1 = System.Math.Abs(fWdU1);
            float fDdU1 = Vector3.Dot(diff, Vector3.UnitY);
            float fADdU1 = System.Math.Abs(fDdU1);

            if(fADdU1 > yExtent && (fDdU1 * fWdU1 >= 0.0f)) {
                result = false;
            }

            float fWdU2 = Vector3.Dot(ray.Direction, Vector3.UnitZ);
            float fAWdU2 = System.Math.Abs(fWdU2);
            float fDdU2 = Vector3.Dot(diff, Vector3.UnitZ);
            float fADdU2 = System.Math.Abs(fDdU2);

            if(fADdU2 > zExtent && (fDdU2 * fWdU2 >= 0.0f)) {
                result = false;
            }

            Vector3 wCrossD = Vector3.Cross(ray.Direction, diff);

            float fAWxDdU0 = System.Math.Abs(Vector3.Dot(wCrossD, Vector3.UnitX));
            float rhs = (yExtent * fAWdU2) + (zExtent * fAWdU1);
            if(fAWxDdU0 > rhs) {
                result = false;
            }

            float fAWxDdU1 = System.Math.Abs(Vector3.Dot(wCrossD, Vector3.UnitY));
            rhs = (xExtent * fAWdU2) + (zExtent * fAWdU0);
            if(fAWxDdU1 > rhs) {
                result = false;
            }

            float fAWxDdU2 = System.Math.Abs(Vector3.Dot(wCrossD, Vector3.UnitZ));
            rhs = (xExtent * fAWdU1) + (yExtent * fAWdU0);
            if(fAWxDdU2 > rhs) {
                result = false;
            }

            result = true;
        }

        /// <summary>
        /// Tests where on the volume the ray intersects, if it does.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <returns>Intersection result</returns>
        public override BoundingIntersectionRecord IntersectsWhere(Ray ray) {
            Vector3 rDir = ray.Direction;
            Vector3 rOrigin = ray.Origin;

            Vector3 diff;
            Vector3.Subtract(ref rOrigin, ref m_center, out diff);

            float t0 = float.NegativeInfinity;
            float t1 = float.PositiveInfinity;

            bool notCulled = Clip(rDir.X, -diff.X - m_extents.X, ref t0, ref t1)
                && Clip(-rDir.X, diff.X - m_extents.X, ref t0, ref t1)
                && Clip(rDir.Y, -diff.Y - m_extents.Y, ref t0, ref t1)
                && Clip(-rDir.Y, diff.Y - m_extents.Y, ref t0, ref t1)
                && Clip(rDir.Z, -diff.Z - m_extents.Z, ref t0, ref t1)
                && Clip(-rDir.Z, diff.Z - m_extents.Z, ref t0, ref t1);

            if(notCulled && (!float.IsNegativeInfinity(t0) || !float.IsPositiveInfinity(t1))) {
                if(t1 > t0) {
                    Vector3 p0;
                    Vector3.Multiply(ref rDir, t0, out p0);
                    Vector3.Add(ref p0, ref rOrigin, out p0);
                    IntersectionRecord rec1 = new IntersectionRecord(p0, t0);

                    Vector3 p1;
                    Vector3.Multiply(ref rDir, t1, out p1);
                    Vector3.Add(ref p1, ref rOrigin, out p1);
                    IntersectionRecord rec2 = new IntersectionRecord(p1, t1);

                    return new BoundingIntersectionRecord(rec1, rec2);
                }

                Vector3 p;
                Vector3.Multiply(ref rDir, t0, out p);
                Vector3.Add(ref p, ref rOrigin, out p);
                IntersectionRecord rec = new IntersectionRecord(p, t0);

                return new BoundingIntersectionRecord(rec);
            }

            return new BoundingIntersectionRecord();
        }

        /// <summary>
        /// Tests where on the volume the ray intersects, if it does. Uses the
        /// Liang-Barsky clipping algorithm as described by David Eberly.
        /// </summary>
        /// <param name="ray">Ray to test</param>
        /// <param name="result">The intersection result</param>
        public override void IntersectsWhere(ref Ray ray, out BoundingIntersectionRecord result) {
            Vector3 rDir = ray.Direction;
            Vector3 rOrigin = ray.Origin;

            Vector3 diff;
            Vector3.Subtract(ref rOrigin, ref m_center, out diff);

            float t0 = float.NegativeInfinity;
            float t1 = float.PositiveInfinity;

            bool notCulled = Clip(rDir.X, -diff.X - m_extents.X, ref t0, ref t1)
                && Clip(-rDir.X, diff.X - m_extents.X, ref t0, ref t1)
                && Clip(rDir.Y, -diff.Y - m_extents.Y, ref t0, ref t1)
                && Clip(-rDir.Y, diff.Y - m_extents.Y, ref t0, ref t1)
                && Clip(rDir.Z, -diff.Z - m_extents.Z, ref t0, ref t1)
                && Clip(-rDir.Z, diff.Z - m_extents.Z, ref t0, ref t1);

            if(notCulled && (!float.IsNegativeInfinity(t0) || !float.IsPositiveInfinity(t1))) {
                if(t1 > t0) {
                    Vector3 p0;
                    Vector3.Multiply(ref rDir, t0, out p0);
                    Vector3.Add(ref p0, ref rOrigin, out p0);
                    IntersectionRecord rec1 = new IntersectionRecord(p0, t0);

                    Vector3 p1;
                    Vector3.Multiply(ref rDir, t1, out p1);
                    Vector3.Add(ref p1, ref rOrigin, out p1);
                    IntersectionRecord rec2 = new IntersectionRecord(p1, t1);

                    result = new BoundingIntersectionRecord(rec1, rec2);
                    return;
                }

                Vector3 p;
                Vector3.Multiply(ref rDir, t0, out p);
                Vector3.Add(ref p, ref rOrigin, out p);
                IntersectionRecord rec = new IntersectionRecord(p, t0);

                result = new BoundingIntersectionRecord(rec);
                return;
            }

            result = new BoundingIntersectionRecord();
        }

        /// <summary>
        /// Test function to determine if a line segment intersects the current test plane.
        /// 
        /// Completely and utterly taken from Eberly
        /// </summary>
        /// <param name="denom">Denominator of the line segment</param>
        /// <param name="numerator">Numerator of the line segment</param>
        /// <param name="t0">First test value</param>
        /// <param name="t1">Second test value</param>
        /// <returns>True if line segment intersects the test plane. False if it is entirely clipped.</returns>
        private bool Clip(float denom, float numerator, ref float t0, ref float t1) {
            if(denom > 0.0f) {
                if(numerator > denom * t1) {
                    return false;
                } else if(numerator > denom * t0) {
                    t0 = numerator / denom;
                }
                return true;
            } else if(denom < 0) {
                if(numerator > denom * t0) {
                    return false;
                } else if(numerator > denom * t1) {
                    t1 = numerator / denom;
                }
                return true;
            }

            return numerator <= 0;
        }

        /// <summary>
        /// Tests if the plane intersects with this bounding volume, and if not
        /// which side the volume is on relative to the plane.
        /// </summary>
        /// <param name="plane">Plane to test</param>
        /// <returns>Plane intersection type</returns>
        public override PlaneIntersectionType Intersects(Plane plane) {
            Vector3 normal = plane.Normal;
            float radius = System.Math.Abs(m_extents.X * normal.X) 
                + System.Math.Abs(m_extents.Y * normal.Y)
                + System.Math.Abs(m_extents.Z * normal.Z);
            float dist;
            Plane.DotCoordinate(ref plane, ref m_center, out dist);
            if(dist < -radius) {
                return PlaneIntersectionType.Front;
            } else if(dist > radius) {
                return PlaneIntersectionType.Back;
            } else {
                return PlaneIntersectionType.Intersects;
            }
        }

        /// <summary>
        /// Tests if the plane intersects with this bounding volume, and if not
        /// which side the volume is on relative to the plane.
        /// </summary>
        /// <param name="plane">Plane to test</param>
        /// <param name="result">Result, the plane intersection type</param>
        public override void Intersects(ref Plane plane, out PlaneIntersectionType result) {
            Vector3 normal = plane.Normal;
            float radius = System.Math.Abs(m_extents.X * normal.X) 
                + System.Math.Abs(m_extents.Y * normal.Y)
                + System.Math.Abs(m_extents.Z * normal.Z);
            float dist;
            Plane.DotCoordinate(ref plane, ref m_center, out dist);
            if(dist < -radius) {
                result = PlaneIntersectionType.Back;
            } else if(dist > radius) {
                result = PlaneIntersectionType.Front;
            } else {
                result = PlaneIntersectionType.Intersects;
            }
        }

        /// <summary>
        /// Determines if the point is contained within this bounding volume
        /// </summary>
        /// <param name="point">Point to test</param>
        /// <returns>Containment type</returns>
        public override ContainmentType Contains(Vector3 point) {
            bool flag = System.Math.Abs(m_center.X - point.X) < m_extents.X
                            && System.Math.Abs(m_center.Y - point.Y) < m_extents.Y
                            && System.Math.Abs(m_center.Z - point.Z) < m_extents.Z;
            if(flag) {
                return ContainmentType.Inside;
            }

            return ContainmentType.Outside;
        }

        /// <summary>
        /// Determines if the box is contained within this bounding volume.
        /// </summary>
        /// <param name="box">AABB to test</param>
        /// <returns>Containment type</returns>
        public override ContainmentType Contains(BoundingBox box) {
            if(box == null) {
                return ContainmentType.Outside;
            }

            Vector3 max = this.Max;
            Vector3 min = this.Min;

            Vector3 bbMin = box.Min;
            Vector3 bbMax = box.Max;

            //Is it outside?
            if(max.X < min.X ||min.X > max.X) {
                return ContainmentType.Outside;
            } else if(max.Y < bbMin.Y || min.Y > bbMax.Y) {
                return ContainmentType.Outside;
            } else if(max.Z < bbMin.Z || min.Z > bbMax.Z) {
                return ContainmentType.Outside;
            }

            //Is it inside?
            if(((min.X <= bbMin.X) && (bbMax.X <= max.X)) 
                && ((min.Y <= bbMin.Y) && (bbMax.Y <= max.Y)) 
                && ((min.Z <= bbMin.Z) && (bbMax.Z <= max.Z))) {
                return ContainmentType.Inside;
            }

            //If neither, then it overlaps
            return ContainmentType.Intersects;
        }

        /// <summary>
        /// Determines if the sphere is contained within this bounding volume.
        /// </summary>
        /// <param name="sphere">Bounding sphere to test</param>
        /// <returns>Containment type</returns>
        public override ContainmentType Contains(BoundingSphere sphere) {
            if(sphere == null) {
                return ContainmentType.Outside;
            }

            Vector3 min = this.Min;
            Vector3 max = this.Max;
            Vector3 sCenter = sphere.Center;
            float sRad = sphere.Radius;

            Vector3 temp;
            float dist;
            Vector3.Clamp(ref sCenter, ref min, ref max, out temp);
            Vector3.Distance(ref sCenter, ref temp, out dist);

            //Check if we're outside
            if(dist > (sRad * sRad)) {
                return ContainmentType.Outside;
            }

            //Or contained
            if(((min.X + sRad) <= sCenter.X)
                && (sCenter.X <= (max.X - sRad))
                && ((max.X - min.X) > sRad)
                && ((min.Y + sRad) <= sCenter.Y)
                && (sCenter.Y <= (max.Y - sRad))
                && ((max.Y - min.Y) > sRad)
                && ((min.Z + sRad) <= sCenter.Z)
                && (sCenter.Z <= (max.Z - sRad))
                && ((max.X - min.X) > sRad)) {
                    return ContainmentType.Inside;
            }

            //Otherwise we overlap
            return ContainmentType.Intersects;
        }

        /// <summary>
        /// Determines if the oriented bounding box is contained within this bounding volume.
        /// </summary>
        /// <param name="obb">Oriented bounding box to test</param>
        /// <returns>Containment type</returns>
        public override ContainmentType Contains(OrientedBoundingBox obb) {
            if(obb == null) {
                return ContainmentType.Outside;
            }

            return obb.Contains(this);
        }

        /// <summary>
        /// Merges this bounding volume with a second one. The resulting bounding
        /// volume is stored locally and will contain the original volumes completely.
        /// </summary>
        /// <param name="volume">Volume to merge with</param>
        public override void MergeLocal(BoundingVolume volume) {
            if(volume == null) {
                return;
            }

            Vector3 center = Vector3.Zero;
            Vector3 extents = Vector3.Zero;
            switch(volume.BoundingType) {
                case BoundingType.AABB:
                    BoundingBox bb = volume as BoundingBox;
                    center = bb.Center;
                    extents = bb.Extents;
                    MergeAABB(ref center, ref extents, this);
                    return;
                case BoundingType.OBB:
                    OrientedBoundingBox obb = volume as OrientedBoundingBox;
                    MergeOBB(obb, this);
                    return;
                case BoundingType.Sphere:
                    BoundingSphere sphere = volume as BoundingSphere;
                    center = sphere.Center;
                    float r = sphere.Radius;
                    extents = new Vector3(r, r, r);
                    MergeAABB(ref center, ref extents, this);
                    return;
            }
        }

        /// <summary>
        /// Merges this bounding volume with a second one. The resulting
        /// bounding volume is returned as a new object and will contain the 
        /// original volumes completely. The returned value will be of the
        /// same bounding type as the caller.
        /// </summary>
        /// <param name="volume">Volume to merge with</param>
        /// <returns>New volume that contains the original two</returns>
        public override BoundingVolume Merge(BoundingVolume volume) {
            if(volume == null) {
                return this.Clone();
            }

            Vector3 center = Vector3.Zero;
            Vector3 extents = Vector3.Zero;
            BoundingBox result = new BoundingBox();
            switch(volume.BoundingType) {
                case BoundingType.AABB:
                    BoundingBox bb = volume as BoundingBox;
                    center = bb.Center;
                    extents = bb.Extents;
                    MergeAABB(ref center, ref extents, result);
                    break;
                case BoundingType.OBB:
                    OrientedBoundingBox obb = volume as OrientedBoundingBox;
                    MergeOBB(obb, result);
                    break;
                case BoundingType.Sphere:
                    BoundingSphere sphere = volume as BoundingSphere;
                    center = sphere.Center;
                    float r = sphere.Radius;
                    extents = new Vector3(r, r, r);
                    MergeAABB(ref center, ref extents, result);
                    break;
            }
            return result;
        }

        /// <summary>
        /// Merge takes care of AABB + Sphere cases. Inputs are of the first volume,
        /// that are used with this AABB's properties to create a merged volume that 
        /// is stored in the result parameter.
        /// </summary>
        /// <param name="center">Center of the first volume.</param>
        /// <param name="extents">Center of the second volume</param>
        /// <param name="result">AABB to store the result in</param>
        private void MergeAABB(ref Vector3 center, ref Vector3 extents, BoundingBox result) {
            float cx = center.X;
            float cy = center.Y;
            float cz = center.Z;

            float x = extents.X;
            float y = extents.Y;
            float z = extents.Z;

            Vector3 max = new Vector3(cx + x, cy + y, cz + x);
            Vector3 min = new Vector3(cx - x, cy - y, cz - x);

            Vector3 ourMax = this.Max;
            Vector3 ourMin = this.Min;
            Vector3.Max(ref ourMax, ref max, out max);
            Vector3.Min(ref ourMin, ref min, out min);

            Vector3 resCenter;
            Vector3.Add(ref max, ref min, out resCenter);
            Vector3.Multiply(ref resCenter, .5f, out resCenter);

            Vector3 resExtents;
            Vector3.Subtract(ref max, ref resCenter, out resExtents);

            result.Center = resCenter;
            result.Extents = resExtents;
        }

        /// <summary>
        /// Merge takes care of OBB case. Input is the OBB to merge with this
        /// AABB to create a merged volume that is stored in the result.
        /// </summary>
        /// <param name="obb">OrientedBoundingBox to merge with</param>
        /// <param name="result">Resulting AABB</param>
        private void MergeOBB(OrientedBoundingBox obb, BoundingBox result) {
            if(!obb.CorrectCorners) {
                obb.ComputeCorners();
            }

            Vector3 min = this.Min;
            Vector3 max = this.Max;

            for(int i = 0; i < obb.Corners.Length; i++) {
                Vector3 temp = obb.Corners[i];
                if(temp.X < min.X) {
                    min.X = temp.X;
                } else if(temp.X > max.X) {
                    max.X = temp.X;
                }
                if(temp.Y < min.Y) {
                    min.Y = temp.Y;
                } else if(temp.Y > max.Y) {
                    max.Y = temp.Y;
                }
                if(temp.Z < min.Z) {
                    min.Z = temp.Z;
                } else if(temp.Z > max.Z) {
                    max.Z = temp.Z;
                }
            }

            Vector3 resCenter;
            Vector3.Add(ref min, ref max, out resCenter);
            Vector3.Multiply(ref resCenter, 0.5f, out resCenter);

            Vector3 resExtents;
            Vector3.Subtract(ref max, ref m_center, out resExtents);

            result.Center = resCenter;
            result.Extents = resExtents;
        }

        /// <summary>
        /// Transforms this volume by the SRT transform. The transformed
        /// volume is stored locally.
        /// </summary>
        /// <param name="transform">SRT transform</param>
        public override void TransformLocal(Transform transform) {
            if(transform == null) {
                return;
            }

            Vector3 scale = transform.Scale;
            Quaternion rotation = transform.Rotation;
            Vector3 translation = transform.Translation;

            Vector3 center;
            Vector3.Multiply(ref m_center, ref scale, out center);
            Vector3.Transform(ref center, ref rotation, out center);
            Vector3.Add(ref translation, ref center, out center);

            Matrix mat = Matrix.FromQuaternion(rotation);

            mat.M11 = System.Math.Abs(mat.M11);
            mat.M12 = System.Math.Abs(mat.M12);
            mat.M13 = System.Math.Abs(mat.M13);
            mat.M21 = System.Math.Abs(mat.M21);
            mat.M22 = System.Math.Abs(mat.M22);
            mat.M23 = System.Math.Abs(mat.M23);
            mat.M31 = System.Math.Abs(mat.M31);
            mat.M32 = System.Math.Abs(mat.M32);
            mat.M33 = System.Math.Abs(mat.M33);

            Vector3 extents;
            Vector3.Multiply(ref m_extents, ref scale, out extents);
            Vector3.Transform(ref extents, ref mat, out extents);

            m_center = center;
            m_extents = extents;
        }

        /// <summary>
        /// Transforms this volume by the SRT transform and creates a 
        /// new bounding volume that contains the result. The bounding volume
        /// will be of the same type as the caller.
        /// </summary>
        /// <param name="transform">SRT transform</param>
        /// <returns>New transformed volume</returns>
        public override BoundingVolume Transform(Transform transform) {
            if(transform == null) {
                return this.Clone();
            }

            Vector3 scale = transform.Scale;
            Quaternion rotation = transform.Rotation;
            Vector3 translation = transform.Translation;

            Vector3 center;
            Vector3.Multiply(ref m_center, ref scale, out center);
            Vector3.Transform(ref center, ref rotation, out center);
            Vector3.Add(ref translation, ref center, out center);

            Matrix mat = Matrix.FromQuaternion(rotation);
   
            mat.M11 = System.Math.Abs(mat.M11);
            mat.M12 = System.Math.Abs(mat.M12);
            mat.M13 = System.Math.Abs(mat.M13);
            mat.M21 = System.Math.Abs(mat.M21);
            mat.M22 = System.Math.Abs(mat.M22);
            mat.M23 = System.Math.Abs(mat.M23);
            mat.M31 = System.Math.Abs(mat.M31);
            mat.M32 = System.Math.Abs(mat.M32);
            mat.M33 = System.Math.Abs(mat.M33);

            Vector3 extents;
            Vector3.Multiply(ref m_extents, ref scale, out extents);
            Vector3.Transform(ref extents, ref mat, out extents);

            return new BoundingBox(center, extents);
        }

        /// <summary>
        /// Serializes this BoundingBox.
        /// </summary>
        /// <param name="output">Output to write to</param>
        public override void Write(ISavableWriter output) {
            base.Write(output);
            output.Write("Extents", m_extents);
        }

        /// <summary>
        /// Deserializes this BoundingBox.
        /// </summary>
        /// <param name="input">Input to read from</param>
        public override void Read(ISavableReader input) {
            base.Read(input);
            m_extents = input.ReadVector3();
        }
    }
}
